/**************************************************************************
 *
 * Copyright (c) 2015-2017  QIKU Company             　　　　　　　            
 *                 奇酷软件（深圳）有限公司  版权所有 2015-2017
 *
 * PROPRIETARY RIGHTS of QIKU Company are involved in the  　　　　　　     
 * subject matter of this material.  All manufacturing, reproduction, use, 
 * and sales rights pertaining to this subject matter are governed by the  
 * license agreement.  The recipient of this software implicitly accepts   
 * the terms of the license.                                               
 * 本软件文档资料是奇酷公司的资产,任何人士阅读和使用本资料必须获得                     
 * 相应的书面授权,承担保密责任和接受相应的法律约束.                                 
 *
 **************************************************************************/
package com.yw.android.common.filelogger;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

/**
 * 将日志输出到文件中
 *
 * @author yuanwei
 */
public class FileAppender implements Appender {
    /**
     * 队列大小
     */
    private static final int MAX_PACKETS = 64;

    /**
     * 日志文件路径
     */
    private String path = null;

    /**
     * 日志文件名
     */
    private String filename = null;

    /**
     * 日志文件后缀
     */
    private String suffix = "";

    /**
     * 单个日志文件最大大小，默认1M
     */
    private long maxFileSize = (1024 * 1024 * 1);

    /**
     * 滚动生成的日志文件数，默认5个
     */
    private int maxRollCount = 5;

    /**
     * 当前正在输出的日志文件绝对文件名
     */
    private String fileUrl = null;

    /**
     * 当前正在输出的日志文件对象
     */
    private File file = null;

    /**
     * 当前正在输出的日志文件流对象
     */
    private FileOutputStream fos = null;

    /**
     * 队列
     */
    private BlockingQueue<String> queue = null;

    /**
     * 持久化日志线程
     */
    private Thread logger = null;

    /**
     * 是否结束
     */
    private boolean done = false;

    /**
     * 时间格式
     */
    private SimpleDateFormat formatter =
            new SimpleDateFormat("yyyy/MM/dd HH:mm:ss.SSS",
                    Locale.getDefault());

    /**
     * 构造函数
     *
     * @param fileUrl 文件绝对路径名
     */
    public FileAppender(String fileUrl) {
        if (empty(fileUrl)) {
            throw new IllegalArgumentException("empty file url");
        }

        this.queue = new ArrayBlockingQueue<String>(MAX_PACKETS, true);
        this.logger = new Thread("file.logger") {
            @Override
            public void run() {
                println("[" + getName() + "] logger thread start ...");
                dumpLogMsgs(this);
                println("[" + getName() + "] logger thread done ...");
            }
        };
        this.logger.start();

        File file = new File(fileUrl);
        this.fileUrl = fileUrl;
        this.path = file.getParent();

        // 获取文件名和后缀
        String name = file.getName();
        int index = name.lastIndexOf(".");

        if ((index >= 0) && (index < name.length())) {
            this.filename = name.substring(0, index);
            this.suffix = name.substring(index);
        } else {
            this.filename = name;
            this.suffix = "";
        }
    }

    /**
     * 设置日志文件新增到多大时，滚动生成新的副本
     *
     * @param size 单个文件日志的大小
     */
    public void setMaxFileSize(int size) {
        if (size > 1024) {
            println("[" + size + "] set max file size ...");
            this.maxFileSize = size;
        } else {
            println("[" + size + "] max file size must larger than 1024 ...");
        }
    }

    /**
     * 设置滚动记录的日志文件的个数
     *
     * @param count 日志文件的个数
     */
    public void setMaxRollCount(int count) {
        if (count > 0) {
            println("[" + count + "] set max roll count ...");
            this.maxRollCount = count;
        } else {
            println("[" + count + "] roll count must larger than 0 ...");
        }
    }

    @Override
    public void close() {
        done = true;
        synchronized (queue) {
            queue.notifyAll();
        }
    }

    @Override
    public void setLogLevel(int level) {
        println("[" + level + "] set log level ...");
    }

    @Override
    public void writeLogMessage(String tag, String level, String msg,
                                Throwable t) {
        writeLogMessage(tag, level, msg + "\n" + StackTracePrinter.getStackTrace(t));
    }

    @Override
    public void writeLogMessage(String tag, String level, String msg) {
        try {
            String logMsg = buildLogMsg(tag, level, msg);
            queue.put(logMsg);
            synchronized (queue) {
                queue.notifyAll();
            }
        } catch (Exception e) {
            println("[" + tag + "][" + level + "][" + msg
                    + "] put log failed(Exception): "
                    + e.getMessage());
        }
    }

    /**
     * 创建封装后的日志内容
     *
     * @param tag   日志标签
     * @param level 日志级别
     * @param msg   日志内容
     * @return 封装后的日志内容
     */
    private String buildLogMsg(String tag, String level, String msg) {
        StringBuffer buffer = new StringBuffer(128);
        buffer.append(nowText())
                .append(" ").append(getLevel(level)).append(" ")
                .append((null == tag) ? "null" : tag).append(" ")
                .append((null == msg) ? "null" : msg).append("\r\n");
        return buffer.toString();
    }

    /**
     * 修正级别标签，占用5个字节
     *
     * @param level 级别标签
     * @return 修正后的级别标签
     */
    private String getLevel(String level) {
        if (null != level) {
            if ("INFO".equals(level)) {
                return "INFO ";
            } else if ("WARN".equals(level)) {
                return "WARN ";
            } else {
                return level;
            }
        } else {
            return "null ";
        }
    }

    /**
     * 将日志持久化写入文件
     *
     * @param t 线程对象
     */
    private void dumpLogMsgs(Thread t) {
        try {
            while (!done) {
                String logMsg = nextLogMsg(t);

                if (null != logMsg) {
                    writeLogMsg(t, logMsg);
                }
            }
        } finally {
            // 确保关闭日志文件
            closeLogFile();
        }
    }

    /**
     * 从队列中获取下一个节点
     *
     * @param t 线程对象
     * @return 下一个节点
     */
    private String nextLogMsg(Thread t) {
        String logMsg = null;

        while ((!done) && (null == (logMsg = queue.poll()))) {
            try {
                synchronized (queue) {
                    queue.wait();
                }
            } catch (InterruptedException e) {
                println("[" + t.getName() + "] poll failed(InterruptedException): "
                        + e.getMessage());
            }
        }

        return logMsg;
    }

    /**
     * 将日志写入文件
     *
     * @param t      当前线程对象
     * @param logMsg 日志信息
     */
    private void writeLogMsg(Thread t, String logMsg) {
        try {
            // 确保打开日志文件
            ensureLogFile();

            if (null != fos) {
                fos.write(logMsg.getBytes("UTF-8"));
                fos.flush();

                // 如果文件超过指定大小，那么滚动记录
                if (file.length() >= maxFileSize) {
                    println("[" + t.getName() + "] file(" + file.length()
                            + ") beyond maxFileSize("
                            + maxFileSize + ") roll log files");
                    rollLogFiles();
                }
            }
        } catch (Exception e) {
            println("[" + t.getName() + "][" + fileUrl
                    + "] write log failed(" + e.getClass().getSimpleName() + "): "
                    + e.getMessage());
        }
    }

    /**
     * 确保日志文件打开
     */
    private void ensureLogFile() {
        if (null == fos) {
            println("[" + fileUrl + "] open log file ...");
            openLogFile();
        }
    }

    /**
     * 打开日志文件
     */
    private void openLogFile() {
        try {
            file = new File(fileUrl);

            // 如果父目录不存在，则创建父目录
            if (!file.getParentFile().exists()) {
                file.getParentFile().mkdirs();
            }

            // 如果文件不存在，则创建文件
            if (!file.exists()) {
                file.createNewFile();
            }

            fos = new FileOutputStream(file, true);
        } catch (IOException e) {
            println("[" + fileUrl + "] open file failed(IOException)", e);
            file = null;
            fos = null;
        }
    }

    /**
     * 滚动所有日志文件
     */
    private void rollLogFiles() {
        // 关闭现有的日志文件
        closeLogFile();

        // 删除最老的日志文件
        // 将其它日志文件依次重命名
        if (maxRollCount > 0) {
            deleteFile(toFileUrl(maxRollCount - 1));

            if (maxRollCount > 1) {
                for (int i = maxRollCount - 2; i >= 0; i--) {
                    renameFile(toFileUrl(i), toFileUrl(i + 1));
                }
            }
        }

        // 重新打开日志文件
        openLogFile();
    }

    /**
     * 关闭当前正在记录的日志文件
     */
    private void closeLogFile() {
        try {
            if (null != fos) {
                println("[" + fileUrl + "] close log file ...");
                fos.close();
            }
        } catch (Exception e) {
            println("[" + fileUrl + "] close file failed(Exception)", e);
        } finally {
            fos = null;
            file = null;
        }
    }

    /**
     * 删除指定文件
     *
     * @param fileUrl 指定文件名
     */
    private void deleteFile(String fileUrl) {
        File file = new File(fileUrl);
        if (file.exists()) {
            println("[" + fileUrl + "] delete file");
            file.delete();
        }
    }

    /**
     * 对文件重命名
     *
     * @param oldFileUrl 旧文件名
     * @param newFileUrl 新文件名
     */
    private void renameFile(String oldFileUrl, String newFileUrl) {
        File file = new File(oldFileUrl);
        if (file.exists()) {
            println("file(" + oldFileUrl + ") rename to file("
                    + newFileUrl + ")");
            file.renameTo(new File(newFileUrl));
        }
    }


    /**
     * 根据下标生成日志文件绝对路径文件名
     * 当index大于0时，格式为[path]/[filename].[index].[suffix]
     * 当index等于0时，直接命名为[path]/[filename].[suffix]
     *
     * @param index 下标
     * @return 绝对路径文件名
     */
    private String toFileUrl(int index) {
        String segment = ((index > 0) ? ("." + index) : "");

        if (path.endsWith(File.separator)) {
            return path + filename + segment + suffix;
        } else {
            return path + File.separator + filename + segment + suffix;
        }
    }

    /**
     * 检测字符串是否为空
     *
     * @param s 指定字符串
     * @return true 为空；false 不为空
     */
    private boolean empty(String s) {
        return ((null == s) || (s.length() <= 0));
    }

    /**
     * 返回当前时间，以字符串形式
     *
     * @return 当前时间
     */
    private String nowText() {
        try {
            return formatter.format(new Date());
        } catch (Exception e) {
            return "";
        }
    }

    /**
     * 打印控制台日志
     *
     * @param text 日志内容
     */
    private void println(String text) {
        System.out.println("[FileAppender] " + text);
    }

    /**
     * 打印控制台日志
     *
     * @param text 日志内容
     * @param t
     */
    private void println(String text, Throwable t) {
        System.out.println("[FileAppender] " + text
                + "\n" + StackTracePrinter.getStackTrace(t));
    }
}

